package com.bocai.grainshop.seckill.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.bocai.common.to.mq.SeckillOrderTo;
import com.bocai.common.utils.R;
import com.bocai.common.vo.MemberVo;
import com.bocai.grainshop.seckill.feign.CouponFeignService;
import com.bocai.grainshop.seckill.feign.ProductFeignService;
import com.bocai.grainshop.seckill.interceptor.SeckillInterceptor;
import com.bocai.grainshop.seckill.service.SeckillService;
import com.bocai.grainshop.seckill.vo.SeckillVo;
import com.bocai.grainshop.seckill.vo.SkuInfoRedisVo;
import com.bocai.grainshop.seckill.vo.SkukillRedisVo;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Service
@Slf4j
public class SeckillServiceImpl implements SeckillService {

    @Autowired
    private CouponFeignService couponFeignService;

    @Autowired
    private StringRedisTemplate redisTemplate;

    private final String SESSION_KEY = "session:key:";

    private final String SECKILL_SHOP_SKU = "seckill:shop:";

    private final String SKU_STOCK_SEMAPHORE = "seckill:stock:";

    @Autowired
    private ProductFeignService productFeignService;

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     * @Author: SunJinbo
     * @Description  上架三天内需要秒杀的商品
     * @Param []
     * @return void
     * @Data Created in 2022/4/28 16:17
     */
    @Override
    public void uploadSeckillForThreeDays() {
        R treeDay = couponFeignService.getSeckillForTreeDay();
        Object code = treeDay.get("code");
        String s = JSON.toJSONString(code);
        Integer codeNum = JSON.parseObject(s, new TypeReference<Integer>() {
        });
        if(codeNum == 0){
            Object data = treeDay.get("data");
            String toJSONString = JSON.toJSONString(data);
            List<SeckillVo> seckillVos = JSON.parseObject(toJSONString, new TypeReference<List<SeckillVo>>() {
            });
            //获取需要上架的数据 存如redis
            //TODO 缓存活动信息
            cacheActivyMsg(seckillVos);
            //TODO 缓存活动需要上架的商品信息
            cacheActivyShopMsg(seckillVos);
        }
    }

    //缓存活动信息
    private void cacheActivyMsg(List<SeckillVo> seckillVos){
        seckillVos.forEach(o->{
            long start = o.getStartTime().getTime();
            long end = o.getEndTime().getTime();
            //拼接key
            String key = SESSION_KEY + start +"_" +end;
            if(!redisTemplate.hasKey(key)){
                //收集出所有的id集合
                List<String> collect = o.getSeckillSkuRelation().stream().map(s -> {
                    return s.getPromotionSessionId()+"_"+s.getSkuId().toString();
                }).collect(Collectors.toList());
                redisTemplate.opsForList().leftPushAll(key,collect);
            }
        });
    }

    //缓存活动需要上架的商品信息
    private void cacheActivyShopMsg(List<SeckillVo> seckillVos){
        //准备redis的hash操作
        BoundHashOperations<String, Object, Object> hashOps = redisTemplate.boundHashOps(SECKILL_SHOP_SKU);
        seckillVos.forEach(o->{
           o.getSeckillSkuRelation().forEach(k->{
               if(!hashOps.hasKey(k.getPromotionSessionId()+"_"+k.getSkuId().toString())){
                   //准备存redis的vo
                   SkukillRedisVo redisVo = new SkukillRedisVo();
                   BeanUtils.copyProperties(k,redisVo);

                   //远程查询改sku_id商品的详细信息
                   R info = productFeignService.info(k.getSkuId());
                   Object code = info.get("code");
                   String s = JSON.toJSONString(code);
                   Integer codeNum = JSON.parseObject(s, new TypeReference<Integer>() {
                   });
                   if(codeNum == 0){
                       Object skuInfo = info.get("skuInfo");
                       String sku = JSON.toJSONString(skuInfo);
                       SkuInfoRedisVo skuInfoRedisVo = JSON.parseObject(sku, new TypeReference<SkuInfoRedisVo>() {
                       });
                       redisVo.setSkuInfoRedisVo(skuInfoRedisVo);
                   }
                   //设置商品上架的秒杀时间
                   redisVo.setStartTime(o.getStartTime().getTime());
                   redisVo.setEndTime(o.getEndTime().getTime());
                   //设置随机码 TODO 放恶意攻击
                   String token = UUID.randomUUID().toString().replace("-", "");
                   redisVo.setRandomCode(token);
                   //TODO 使用库存作为分布式信号量 作用：限流
                   RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + token);
                   //TODO 商品可以秒杀的数量作为信号量
                   semaphore.trySetPermits(k.getSeckillCount().intValue());
                   //转为json传redis
                   String redisVoJson = JSON.toJSONString(redisVo);
                   hashOps.put(k.getPromotionSessionId()+"_"+k.getSkuId().toString(),redisVoJson);
               }
           });
        });
    }

    //获取当前可以秒杀的商品
    @Override
    public List<SkukillRedisVo> getSeckillNowShop() {
        //获取当前时间
        long time = new Date().getTime();
        //查询所有的秒杀场次的key
        Set<String> keys = redisTemplate.keys(SESSION_KEY + "*");
        if(keys.size()>0 && keys != null){
            for(String o : keys){
                //session:key:1651093200000_1651093260000
                String replace = o.replace("session:key:", "");
                String[] s = replace.split("_");
                if (time >= Long.parseLong(s[0]) && time <= Long.parseLong(s[1])) {
                    //如果是这个时间区间的 redis中取出场次
                    List<String> range = redisTemplate.opsForList().range(o, -100, 100);
                    //查询redis这个场次的所有商品信息
                    BoundHashOperations<String, String, String> ops = redisTemplate.boundHashOps(SECKILL_SHOP_SKU);
                    List<String> list = ops.multiGet(range);
                    //处理返回结果
                    if(list != null){
                        List<SkukillRedisVo> collect = list.stream().map(k -> {
                            SkukillRedisVo redisVo = JSON.parseObject(k, SkukillRedisVo.class);
                            return redisVo;
                        }).collect(Collectors.toList());
                        return collect;
                    }
                    break;
                }
            }
        }
        return null;
    }

    /**
     * @Author: SunJinbo
     * @Description  获取秒杀信心通过skuId
     * @Param [skuId]
     * @return com.bocai.grainshop.seckill.vo.SkukillRedisVo
     * @Data Created in 2022/4/30 17:33
     */
    @Override
    public SkukillRedisVo getSeckillMsgByskuId(Long skuId) {
        BoundHashOperations<String, String, String> ops = redisTemplate.boundHashOps(SECKILL_SHOP_SKU);
        Set<String> keys = ops.keys();
        if(keys.size() > 0 && keys != null){
            //3_1
            for (String s : keys) {
                String[] split = s.split("_");
                String Id = split[1];
                if(Long.parseLong(Id) == skuId){
                    String msg = ops.get(s);
                    if(msg != null){
                        SkukillRedisVo redisVo = JSON.parseObject(msg, SkukillRedisVo.class);
                        long time = new Date().getTime();
                        if(time >= redisVo.getStartTime() && time<= redisVo.getEndTime()){
                        }else {
                            redisVo.setRandomCode(null);
                        }
                        return redisVo;
                    }
                }
            }
        }
        return null;
    }

    /**
     * @Author: SunJinbo
     * @Description  秒杀
     * @Param [killId, key, num]
     * @return java.lang.String      
     * @Data Created in 2022/4/30 17:29
     */
    @Override
    public String seckill(String killId, String key, Integer num) {
        //拦截器中获取登录用户的信息
        MemberVo memberVo = SeckillInterceptor.loginUser.get();
        //从redis中获取需要秒杀的数据
        BoundHashOperations<String, String, String> ops = redisTemplate.boundHashOps(SECKILL_SHOP_SKU);
        String replace = killId.replace("-", "_");
        String data = ops.get(replace);
        if(StringUtils.hasText(data)){
            //如果有数据 转成对象
            SkukillRedisVo skukillRedisVo = JSON.parseObject(data, SkukillRedisVo.class);
            //TODO 开始验证合法性
            //验证时间 是否是秒杀时间范围
            long time = new Date().getTime();
            if(time >= skukillRedisVo.getStartTime() && time <= skukillRedisVo.getEndTime()){
                //如果在秒杀时间 开启校验随机码是否匹配
                String randomCode = skukillRedisVo.getRandomCode();
                Long skuId = skukillRedisVo.getSkuId();
                String redisKey = skukillRedisVo.getPromotionSessionId() + "_" + skuId;
                if(key.equalsIgnoreCase(randomCode) && replace.equalsIgnoreCase(redisKey)){
                    //如果随机码和skuId同时匹配上  TODO 验证是否购买数量是否符合要求
                    if(num <= skukillRedisVo.getSeckillLimit().intValue()){
                        //TODO 购买幂等性 防止一个人多次购买 购买一次就去redis占个位 userId_sessionId_skuId作为一个key
                        //准备占坑的key
                        String SETNX_KEY = memberVo.getId()+"_"+ redisKey;
                        //设置过期时间
                        long ttl = skukillRedisVo.getEndTime() - skukillRedisVo.getStartTime();
                        Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(SETNX_KEY, num.toString(), ttl, TimeUnit.MILLISECONDS);
                        if(aBoolean){
                            //如果占到坑位 TODO 说明重来没有购买过
                            RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + randomCode);
                            try {
                                //尝试获取信号量
                                boolean b = semaphore.tryAcquire(num, 10, TimeUnit.MILLISECONDS);
                                if(b){
                                    //生成一个订单返回
                                    String order_Sn = UUID.randomUUID().toString().replace("-", "");
                                    //TODO 向mq发送发送消息 后续慢慢后台创建订单
                                    SeckillOrderTo seckillOrderTo = new SeckillOrderTo();
                                    seckillOrderTo.setOrderSn(order_Sn);
                                    seckillOrderTo.setMemberId(memberVo.getId());
                                    seckillOrderTo.setNum(num);
                                    seckillOrderTo.setUsername(memberVo.getNickname());
                                    seckillOrderTo.setPromotionSessionId(skukillRedisVo.getPromotionSessionId());
                                    seckillOrderTo.setSeckillPrice(skukillRedisVo.getSeckillPrice());
                                    seckillOrderTo.setSkuId(skukillRedisVo.getSkuId());
                                    rabbitTemplate.convertAndSend("order-event-exchange","order.seckill.order",seckillOrderTo);
                                    return order_Sn;
                                }else {
                                    //获取信号量失败
                                    return null;
                                }
                            } catch (Exception e) {
                                //获取失败
                                return null;
                            }
                        }else {
                            //如果失败
                            return null;
                        }
                    }else {
                        //买的数量不合法
                        return null;
                    }
                }else {
                    //如果随机码不匹配 直接返回null
                    return null;
                }
            }else {
                //如果不在秒杀时间 防止恶意请求
                return null;
            }
        }else {
            //redis中没数据直接返回空
        }
        return null;
    }
}
